//	--------------------------------------------------------------------
//	Copyright(C) 2006,2007 Zhao Yukun. All rights reserved.
//	This file is a part of the Xeres system.
//	Xeres 2007
//	Contact : xeres.engine@gmail.com
//	$(reservedInfo)
//	$(reservedDeclaration)
//	--------------------------------------------------------------------
///	@file	<enumeration10.cpp>
///	@path	~/src/graphics/dx10/
///	@date	2007/09/19
///	@desc	Enumerates available Direct3D10 adapters, devices, modes, etc.

#include "config/config.h"

#include "graphics/dx10/enumeration10.h"
#include "graphics/dx10/CDevice10.h"
#include "graphics/dx10/import_dx10.h"

#include <strsafe.h>

namespace xeres {

	// get instance of EnumD3D10
	EnumD3D10& EnumD3D10::instance( void )
	{
		static EnumD3D10 s_EnumD3D10_instance;
		return s_EnumD3D10_instance;
	}

	// ctor
	EnumD3D10::EnumD3D10( void )
		: m_inited( false )
		, m_minWidth( 640 )
		, m_maxWidth( UINT_MAX )
		, m_minHeight( 480 )
		, m_maxHeight( UINT_MAX )
		, m_refreshMin( 0 )
		, m_refreshMax( UINT_MAX )
		, m_multisampleQualityMax( UINT_MAX )
	{
		m_depthStencilPossibleList.clear();
		m_depthStencilPossibleList.push_back( DXGI_FORMAT_D32_FLOAT_S8X24_UINT );
		m_depthStencilPossibleList.push_back( DXGI_FORMAT_D32_FLOAT );
		m_depthStencilPossibleList.push_back( DXGI_FORMAT_D24_UNORM_S8_UINT );
		m_depthStencilPossibleList.push_back( DXGI_FORMAT_D16_UNORM );
	}

	// dtor
	EnumD3D10::~EnumD3D10( void )
	{
		if( !m_inited )
			Finalize();
	}

	namespace
	{
		class _DXGIFactoryProtector
		{
		public:
			// ctor
			_DXGIFactoryProtector( void )
			{
				HRESULT hr = dx10::CreateDXGIFactory( IID_IDXGIFactory , (LPVOID*)( &m_dxgiFactory ) );
				if( FAILED(hr) )
				{
					// FIXME: trace error
					FATAL_ABORT( _S("Failed to create DXGI Factory.") );
				}
			}
			~_DXGIFactoryProtector( void )
			{
				m_dxgiFactory->Release();
			}

			// data
			IDXGIFactory * m_dxgiFactory;
		};
	}

	// initialize
	void EnumD3D10::Initialize( bool bEnumerateAllAdapterFormats )
	{
		if( m_inited )
			return;

		_DXGIFactoryProtector factory;

		m_enumerateAllAdapterFormats = bEnumerateAllAdapterFormats;

		HRESULT hr;
		m_inited = true;

		for( int index = 0; ; ++index )
		{
			IDXGIAdapter * pAdapter = NULL;
			hr = factory.m_dxgiFactory->EnumAdapters( index, &pAdapter );
			if( FAILED(hr) )
				break;

			EnumAdapterInfo10 * pAdapterInfo = new EnumAdapterInfo10;
			memset( pAdapterInfo , 0 , sizeof(EnumAdapterInfo10) );
			pAdapterInfo->m_adapterOrdinal = index;
			pAdapter->GetDesc( &pAdapterInfo->m_adapterDesc );
			pAdapterInfo->m_adapter = pAdapter;

			// Enumerate the device driver types on the adapter.
			hr = EnumerateDevices( pAdapterInfo );
			if( FAILED( hr ) )
			{
				delete pAdapterInfo;
				continue;
			}

			hr = EnumerateOutputs( pAdapterInfo );
			if( FAILED( hr ) || pAdapterInfo->m_outputInfoList.size() <= 0 )
			{
				delete pAdapterInfo;
				continue;
			}

			// Get info for each settings(combo) on this device
			if( FAILED( hr = EnumerateDeviceSettings( factory.m_dxgiFactory , pAdapterInfo ) ) )
			{
				delete pAdapterInfo;
				continue;
			}

			m_adapterInfoList.push_back( pAdapterInfo );
		}

		//
		// Check for 2 or more adapters with the same name. Append the name
		// with some instance number if that's the case to help distinguish
		// them.
		//
		bool bUniqueDesc = true;
		EnumAdapterInfo10 * pAdapterInfo;
		for( int i = 0; i < (int)m_adapterInfoList.size(); i++ )
		{
			EnumAdapterInfo10 * pAdapterInfo1 = m_adapterInfoList[i];

			for( int j = i+1; j < (int)m_adapterInfoList.size(); j++ )
			{
				EnumAdapterInfo10 * pAdapterInfo2 = m_adapterInfoList[j];
				if( wcsncmp(
					pAdapterInfo1->m_adapterDesc.Description ,
					pAdapterInfo2->m_adapterDesc.Description ,
					DXGI_MAX_DEVICE_IDENTIFIER_STRING ) == 0 )
				{
					bUniqueDesc = false;
					break;
				}
			}

			if( !bUniqueDesc )
				break;
		}

		for( int i = 0; i < (int)m_adapterInfoList.size(); i++ )
		{
			pAdapterInfo = m_adapterInfoList[i];

			StringCchCopy( pAdapterInfo->m_uniqueDescription , 100 , pAdapterInfo->m_adapterDesc.Description );
			if( !bUniqueDesc )
			{
				WCHAR sz[100];
				StringCchPrintf( sz , 100 , _S(" (#%d)") , pAdapterInfo->m_adapterOrdinal );
				StringCchCat( pAdapterInfo->m_uniqueDescription , DXGI_MAX_DEVICE_IDENTIFIER_STRING , sz );
			}
		}
	}

	// finalize
	void EnumD3D10::Finalize( void )
	{
		ClearAdapterInfoList();
		m_inited = false;
	}

	HRESULT EnumD3D10::EnumerateOutputs( EnumAdapterInfo10* pAdapterInfo )
	{
		HRESULT hr;
		IDXGIOutput *pOutput;

		for( int iOutput = 0; ; ++iOutput )
		{
			pOutput = NULL;
			hr = pAdapterInfo->m_adapter->EnumOutputs( iOutput, &pOutput );
			if( DXGI_ERROR_NOT_FOUND == hr )
			{
				return S_OK;
			}
			else if (FAILED(hr))
			{
				return hr;	//Something bad happened.
			}
			else //Success!
			{
				EnumOutputInfo10 *pOutputInfo = new EnumOutputInfo10;
				memset( pOutputInfo , 0 , sizeof(EnumOutputInfo10) );
				pOutput->GetDesc( &pOutputInfo->m_desc );
				pOutputInfo->m_output = iOutput;
				pOutputInfo->m_pOutput = pOutput;

				EnumerateDisplayModes( pOutputInfo );
				if( pOutputInfo->m_displayModeList.size() <= 0 )
				{
					// If this output has no valid display mode, do not save it.
					delete pOutputInfo;
					continue;
				}

				pAdapterInfo->m_outputInfoList.push_back( pOutputInfo );
			}
		}
	}

	//--------------------------------------------------------------------------------------
	HRESULT EnumD3D10::EnumerateDisplayModes( EnumOutputInfo10 * pOutputInfo )
	{
		HRESULT hr = S_OK;
		const DXGI_FORMAT allowedAdapterFormatArray[] = 
		{
			DXGI_FORMAT_R8G8B8A8_UNORM,			//This is DXUT's preferred mode

			DXGI_FORMAT_R16G16B16A16_FLOAT,
			DXGI_FORMAT_R10G10B10A2_UNORM,
			DXGI_FORMAT_R8G8B8A8_UNORM_SRGB,
		};
		int allowedAdapterFormatArrayCount  = sizeof(allowedAdapterFormatArray) / sizeof(allowedAdapterFormatArray[0]);

		// The fast path only enumerates R8G8B8A8_UNORM modes
		if( !m_enumerateAllAdapterFormats )
			allowedAdapterFormatArrayCount = 1;

		for( int f = 0; f < allowedAdapterFormatArrayCount; ++f )
		{
			// Fast-path: Try to grab at least 512 modes.
			//			  This is to avoid calling GetDisplayModeList more times than necessary.
			//			  GetDisplayModeList is an expensive call.
			UINT NumModes = 512;
			DXGI_MODE_DESC *pDesc = new DXGI_MODE_DESC[ NumModes ];
			hr = pOutputInfo->m_pOutput->GetDisplayModeList(allowedAdapterFormatArray[f],
				0,
				&NumModes,
				pDesc );
			if( DXGI_ERROR_NOT_FOUND == hr )
			{
				delete []pDesc;
				pDesc = NULL;;
				NumModes = 0;
				break;
			}
			else if( MAKE_DXGI_HRESULT( 34 ) == hr && DXGI_FORMAT_R8G8B8A8_UNORM == allowedAdapterFormatArray[f] )
			{
				// DXGI cannot enumerate display modes over a remote session.  Therefore, create a fake display
				// mode for the current screen resolution for the remote session.
				if( 0 != GetSystemMetrics( SM_REMOTESESSION ) ) // SM_REMOTESESSION
				{
					DEVMODE DevMode;
					DevMode.dmSize = sizeof(DEVMODE);
					if( EnumDisplaySettings( NULL, ENUM_CURRENT_SETTINGS, &DevMode ) )
					{
						NumModes = 1;
						pDesc[0].Width = DevMode.dmPelsWidth;
						pDesc[0].Height = DevMode.dmPelsHeight;
						pDesc[0].Format = DXGI_FORMAT_R8G8B8A8_UNORM;
						pDesc[0].RefreshRate.Numerator = 60;
						pDesc[0].RefreshRate.Denominator = 1;
						pDesc[0].ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_PROGRESSIVE;
						pDesc[0].Scaling = DXGI_MODE_SCALING_CENTERED;
						hr = S_OK;
					}
				}
			}
			else if( DXGI_ERROR_MORE_DATA == hr )
			{
				// Slow path.  There were more than 512 modes.
				delete [] pDesc;
				pDesc = NULL;
				hr = pOutputInfo->m_pOutput->GetDisplayModeList(allowedAdapterFormatArray[f],
					0,
					&NumModes,
					NULL );
				if( FAILED(hr) )
				{
					delete [] pDesc;
					pDesc = NULL;
					NumModes = 0;
					break;
				}

				DXGI_MODE_DESC *pDesc = new DXGI_MODE_DESC[ NumModes ];

				hr = pOutputInfo->m_pOutput->GetDisplayModeList(allowedAdapterFormatArray[f],
					0,
					&NumModes,
					pDesc );
				if( FAILED(hr) )
				{
					delete [] pDesc;
					pDesc = NULL;
					NumModes = 0;
					break;
				}

			}

			if( 0 == NumModes && 0 == f )
			{
				// No R8G8B8A8_UNORM modes!
				// Abort the fast-path if we're on it
				allowedAdapterFormatArrayCount = sizeof(allowedAdapterFormatArray) / sizeof(allowedAdapterFormatArray[0]);
				delete [] pDesc;
				pDesc = NULL;
				continue;
			}

			if( SUCCEEDED( hr ) )
			{
				for( UINT m=0; m<NumModes; m++ )
				{
					pOutputInfo->m_displayModeList.push_back( pDesc[m] );
				}
			}

			delete [] pDesc;
			pDesc = NULL;
		}

		return hr;
	}

	//--------------------------------------------------------------------------------------
	HRESULT EnumD3D10::EnumerateDevices( EnumAdapterInfo10 * pAdapterInfo )
	{
		HRESULT hr;

		const D3D10_DRIVER_TYPE devTypeArray[] =
		{ 
			D3D10_DRIVER_TYPE_HARDWARE,
			D3D10_DRIVER_TYPE_REFERENCE,
		};
		const UINT devTypeArrayCount = sizeof(devTypeArray) / sizeof(devTypeArray[0]);

		// Enumerate each Direct3D device type
		for( UINT iDeviceType = 0; iDeviceType < devTypeArrayCount; iDeviceType++ )
		{
			EnumDeviceInfo10 * pDeviceInfo = new EnumDeviceInfo10;
			if( pDeviceInfo == NULL )
				return E_OUTOFMEMORY;

			// Fill struct w/ AdapterOrdinal and D3DX10_DRIVER_TYPE
			pDeviceInfo->m_adapterOrdinal = pAdapterInfo->m_adapterOrdinal;
			pDeviceInfo->m_deviceType = devTypeArray[iDeviceType];

			// Call D3D10CreateDevice to ensure that this is a D3D10 device.
			ID3D10Device * pd3dDevice = NULL;
			IDXGIAdapter * pAdapter = NULL;
			if( devTypeArray[iDeviceType] == D3D10_DRIVER_TYPE_HARDWARE )
				pAdapter = pAdapterInfo->m_adapter;

			hr = dx10::D3D10CreateDevice( pAdapter, devTypeArray[iDeviceType], 0, NULL, D3D10_SDK_VERSION, &pd3dDevice );

			if( FAILED( hr ) )
			{
				delete pDeviceInfo;
				continue;
			}

			if( devTypeArray[iDeviceType] != D3D10_DRIVER_TYPE_HARDWARE )
			{
				IDXGIDevice* pDXGIDev = NULL;
				hr = pd3dDevice->QueryInterface( IID_IDXGIDevice , (LPVOID*)( &pDXGIDev ) );
				if( SUCCEEDED(hr) && pDXGIDev )
				{
					pAdapterInfo->m_adapter->Release();
					pAdapterInfo->m_adapter = NULL;
					pDXGIDev->GetAdapter( &pAdapterInfo->m_adapter );
				}
				pDXGIDev->Release();
				pDXGIDev = NULL;
			}

			pd3dDevice->Release();
			pd3dDevice = NULL;
			pAdapterInfo->m_deviceInfoList.push_back( pDeviceInfo );
		}

		return S_OK;
	}

	//--------------------------------------------------------------------------------------
	HRESULT EnumD3D10::EnumerateDeviceSettings( IDXGIFactory * pFactory, EnumAdapterInfo10 * pAdapterInfo )
	{
		// Iterate through each combination of device driver type, output,
		// adapter format, and backbuffer format to build the adapter's device combo list.
		//

		for( int output = 0; output < (int)pAdapterInfo->m_outputInfoList.size(); ++output )
		{
			EnumOutputInfo10 * pOutputInfo = pAdapterInfo->m_outputInfoList[ output ];

			for( int device = 0; device < (int)pAdapterInfo->m_deviceInfoList.size(); ++device )
			{
				EnumDeviceInfo10 * pDeviceInfo = pAdapterInfo->m_deviceInfoList[ device ];

				const DXGI_FORMAT backBufferFormatArray[] = 
				{
					DXGI_FORMAT_R8G8B8A8_UNORM,		//This is DXUT's preferred mode

					DXGI_FORMAT_R16G16B16A16_FLOAT,
					DXGI_FORMAT_R10G10B10A2_UNORM,
					DXGI_FORMAT_R8G8B8A8_UNORM_SRGB,
				};
				const UINT backBufferFormatArrayCount = sizeof(backBufferFormatArray) / sizeof(backBufferFormatArray[0]);

				for( UINT iBackBufferFormat = 0; iBackBufferFormat < backBufferFormatArrayCount; iBackBufferFormat++ )
				{
					DXGI_FORMAT backBufferFormat = backBufferFormatArray[iBackBufferFormat];

					for( int nWindowed = 0; nWindowed < 2; nWindowed++ )
					{
						if( !nWindowed && pOutputInfo->m_displayModeList.size() == 0 )
							continue;

						// determine if there are any modes for this particular format
						UINT iModes = 0;
						for( int i = 0 ; i < (int)pOutputInfo->m_displayModeList.size() ; i++ )
						{
							if( backBufferFormat == pOutputInfo->m_displayModeList[i].Format )
								iModes ++;
						}
						if( 0 == iModes )
							continue;

						// If an application callback function has been provided, make sure this device
						// is acceptable to the app.
//						if( m_IsD3D10DeviceAcceptableFunc != NULL )
//						{
//							if( !m_IsD3D10DeviceAcceptableFunc( pAdapterInfo->AdapterOrdinal, output, pDeviceInfo->DeviceType, backBufferFormat, FALSE != nWindowed, m_pIsD3D10DeviceAcceptableFuncUserContext ) )
//								continue;
//						}

						// At this point, we have an adapter/device/backbufferformat/iswindowed
						// DeviceCombo that is supported by the system. We still 
						// need to find one or more suitable depth/stencil buffer format,
						// multisample type, and present interval.
						EnumDeviceSettings10 * pDeviceSettings = new EnumDeviceSettings10;
						pDeviceSettings->m_adapterOrdinal = pDeviceInfo->m_adapterOrdinal;
						pDeviceSettings->m_deviceType = pDeviceInfo->m_deviceType;
						pDeviceSettings->m_backBufferFormat = backBufferFormat;
						pDeviceSettings->m_windowed = (nWindowed != 0);
						pDeviceSettings->m_output = pOutputInfo->m_output;
						pDeviceSettings->m_adapterInfo = pAdapterInfo;
						pDeviceSettings->m_deviceInfo = pDeviceInfo;
						pDeviceSettings->m_outputInfo = pOutputInfo;

						BuildMultiSampleQualityList( pFactory , backBufferFormat , pDeviceSettings );

						pAdapterInfo->m_deviceSettingsList.push_back( pDeviceSettings );
					}
				}
			}
		}

		return S_OK;
	}

	//--------------------------------------------------------------------------------------
	// Release all the allocated EnumAdapterInfo10 objects and empty the list
	//--------------------------------------------------------------------------------------
	void EnumD3D10::ClearAdapterInfoList()
	{
		EnumAdapterInfo10 * pAdapterInfo;
		for( int i = 0 ; i < (int)m_adapterInfoList.size() ; ++i )
		{
			pAdapterInfo = m_adapterInfoList[i];
			delete pAdapterInfo;
		}

		m_adapterInfoList.clear();
	}

	//--------------------------------------------------------------------------------------
	void EnumD3D10::BuildMultiSampleQualityList( IDXGIFactory * pFactory , DXGI_FORMAT fmt, EnumDeviceSettings10 * pDeviceSettings )
	{
		ID3D10Device* pd3dDevice = NULL;
		IDXGIAdapter* pAdapter = NULL;
		if( pDeviceSettings->m_deviceType == D3D10_DRIVER_TYPE_HARDWARE )
			pFactory->EnumAdapters( pDeviceSettings->m_adapterInfo->m_adapterOrdinal , &pAdapter );

		if( FAILED( dx10::D3D10CreateDevice( pAdapter , pDeviceSettings->m_deviceType , 0 , NULL , D3D10_SDK_VERSION , &pd3dDevice ) ) )
			return;

		for( int i = 1; i <= D3D10_MAX_MULTISAMPLE_SAMPLE_COUNT ; ++i )
		{
			UINT Quality;
			if( SUCCEEDED( pd3dDevice->CheckMultisampleQualityLevels( fmt, i, &Quality ) ) && Quality > 0 )
			{
				pDeviceSettings->m_multiSampleCountList.push_back( i );
				pDeviceSettings->m_multiSampleQualityList.push_back( Quality );
			}
		}

		if( pd3dDevice )
			pd3dDevice->Release();
	}

	const std::vector<EnumAdapterInfo10*>& EnumD3D10::GetAdapterInfoList( void ) const
	{
		return m_adapterInfoList;
	}


	//--------------------------------------------------------------------------------------
	const EnumAdapterInfo10 * EnumD3D10::GetAdapterInfo( UINT AdapterOrdinal ) const
	{
		for( int iAdapter = 0; iAdapter < (int)m_adapterInfoList.size(); iAdapter++ )
		{
			const EnumAdapterInfo10* pAdapterInfo = m_adapterInfoList[iAdapter];
			if( pAdapterInfo->m_adapterOrdinal == AdapterOrdinal )
				return pAdapterInfo;
		}
		return NULL;
	}


	//--------------------------------------------------------------------------------------
	const EnumDeviceInfo10 * EnumD3D10::GetDeviceInfo( UINT AdapterOrdinal, D3D10_DRIVER_TYPE DeviceType ) const
	{
		const EnumAdapterInfo10* pAdapterInfo = GetAdapterInfo( AdapterOrdinal );
		if( pAdapterInfo )
		{
			for( int iDeviceInfo = 0; iDeviceInfo < (int)pAdapterInfo->m_deviceInfoList.size(); iDeviceInfo++ )
			{
				const EnumDeviceInfo10* pDeviceInfo = pAdapterInfo->m_deviceInfoList[ iDeviceInfo ];
				if( pDeviceInfo->m_deviceType == DeviceType )
					return pDeviceInfo;
			}
		}

		return NULL;
	}


	//--------------------------------------------------------------------------------------
	const EnumOutputInfo10 * EnumD3D10::GetOutputInfo( UINT AdapterOrdinal, UINT Output ) const
	{
		const EnumAdapterInfo10 * pAdapterInfo = GetAdapterInfo( AdapterOrdinal );
		if( pAdapterInfo && pAdapterInfo->m_outputInfoList.size() > int(Output) )
		{
			return pAdapterInfo->m_outputInfoList[ Output ];
		}

		return NULL;
	}


	//--------------------------------------------------------------------------------------
	const EnumDeviceSettings10 * EnumD3D10::GetDeviceSettings( UINT AdapterOrdinal, D3D10_DRIVER_TYPE DeviceType,
		UINT Output, DXGI_FORMAT BackBufferFormat, BOOL Windowed ) const
	{
		const EnumAdapterInfo10* pAdapterInfo = GetAdapterInfo( AdapterOrdinal );
		if( pAdapterInfo )
		{
			for( int iDeviceCombo = 0; iDeviceCombo < (int)pAdapterInfo->m_deviceSettingsList.size(); iDeviceCombo++ )
			{
				const EnumDeviceSettings10 * pDeviceSettingsCombo = pAdapterInfo->m_deviceSettingsList[iDeviceCombo];
				if( pDeviceSettingsCombo->m_backBufferFormat == BackBufferFormat &&
					pDeviceSettingsCombo->m_windowed == Windowed )
					return pDeviceSettingsCombo;
			}
		}

		return NULL;
	}


	//--------------------------------------------------------------------------------------
	EnumOutputInfo10::~EnumOutputInfo10( void )
	{
		m_pOutput->Release();
		m_displayModeList.clear();
	}

	//--------------------------------------------------------------------------------------
	EnumDeviceInfo10::~EnumDeviceInfo10( void )
	{
	}

	//--------------------------------------------------------------------------------------
	EnumAdapterInfo10::~EnumAdapterInfo10( void )
	{
		for( int i=0; i < (int)m_outputInfoList.size(); i++ )
		{
			EnumOutputInfo10* pOutputInfo = m_outputInfoList[i];
			delete pOutputInfo;
		}
		for( int i = 0; i < (int)m_deviceInfoList.size(); ++i )
		{
			EnumDeviceInfo10* pDeviceInfo = m_deviceInfoList[i];
			delete pDeviceInfo;
		}
		for( int i = 0; i < (int)m_deviceSettingsList.size(); ++i )
		{
			EnumDeviceSettings10* pDeviceCombo = m_deviceSettingsList[i];
			delete pDeviceCombo;
		}
		m_adapter->Release();
	}


} // namespace xeres
